package com.example.snowflake.demo1;

public class SnowFlake {


    /**
     * 起始的时间戳 2016-11-26 21:21:05
     */
    private final static long START_STAMP = 1480166465631L;
    
    /**
     * 每一部分占用的位数
     */
    private final static long SEQUENCE_BIT = 12; // 序列号占用的位数
    private final static long MACHINE_BIT = 5; // 机器标识占用的位数
    private final static long DATA_CENTER_BIT = 5;// 数据中心占用的位数
    
    /**
     * 每一部分的最大值
     * MAX_DATA_CENTER_NUM的计算过程
     *  DATA_CENTER_BIT=5,对应的补码: 0000 0101
     *  -1L对应的补码: 1111 1111
     *  -1L <<5 后:1110 0000
     *  -1L ^ 1110 0000后 0001 1111
     *  0001 1111  等于31
     */

    // 机房的最大id,也就是机房的最大数量
    // 这里是31
    private final static long MAX_DATA_CENTER_NUM = -1L ^ (-1L << DATA_CENTER_BIT);
    // 机器的最大id,也就是机器的最大数量
    // 这里是31
    private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
    // 序列号的最大值
    // 这里是 4095
    private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
    
    /**
     * 每一部分向左的位移
     */
    // 机器的二进制数据左位移  12位
    private final static long MACHINE_LEFT = SEQUENCE_BIT;
    // 机房的二进制数据左位移  17位
    private final static long DATA_CENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
    // 时间戳的二进制数据左位移 22位
    private final static long TIMESTAMP_LEFT = DATA_CENTER_LEFT + DATA_CENTER_BIT;

    // 机房ID,5个bit,最大2^5=32,0到31,即(0000 0000-0001 1111)
    private long dataCenterId;
    // 机器ID,5个bit,最大2^5=32,0到31,即(0000 0000-0001 1111)
    private long machineId;
    // 序列号:12个bit,最大2^12=4096,0到4096,即(0000 0000 0000-1111 1111 1111)
    // 代表一毫秒内生成多少个序号,可以生成4096个,因为第1个0不需要生成。所以真正可以生成4095个
    private long sequence = 0L;
    // 上一次时间戳
    private long lastStamp = -1L;
    
    public SnowFlake(long dataCenterId, long machineId) {
        // 检查机房id是否超过31 不能小于0
        if (dataCenterId > MAX_DATA_CENTER_NUM || dataCenterId < 0) {
            throw new IllegalArgumentException("机房id的id不能超过最大值,也不能为0");
        }
        // 检查机器id是否超过31 不能小于0
        if (machineId > MAX_MACHINE_NUM || machineId < 0) {
            throw new IllegalArgumentException("机器id的id不能超过最大值,也不能为0");
        }
        // 设置当前机房id
        this.dataCenterId = dataCenterId;
        // 设置当前机器id
        this.machineId = machineId;
    }
    
    /**
     * 让当前这台机器上的snowflake算法程序生成一个全局唯一的id
     * @return
     */
    public synchronized long nextId() {
        // 获得当前时间的毫秒数
        long currStamp = getNewStamp();
        if (currStamp < lastStamp) {
            throw new RuntimeException("服务器的时钟向后移动,拒绝生成id");
        }

        // 假设在同一个毫秒内，又发送了一个请求生成一个id
        // 这个时候就得把seqence序号给递增1，最多就是4095
        if (currStamp == lastStamp) {
            // 相同毫秒内,序列号自增,且保证取值范围最大为MAX_SEQUENCE
            sequence = (sequence + 1) & MAX_SEQUENCE;
            // 同一毫秒的序列数已经达到最大，也就是产生的id数 超过4095，系统会进入等待，直到下一毫秒，系统继续产生ID
            if (sequence == 0L) { // 即：已经超出MAX_SEQUENCE 即：1 0000 0000 0000
                currStamp = getNextMill();
            }
        } else {
            // 不同毫秒内，序列号置为0
            sequence = 0L;
        }

        // 这儿记录一下最近一次生成id的时间戳，单位是毫秒
        lastStamp = currStamp;
        // 这儿就生成一个64bit id的二进制位运算操作

        System.out.println("时间戳:" + (currStamp - START_STAMP));
        System.out.println("时间戳左移位数:" + (TIMESTAMP_LEFT));
        System.out.println("机房id:" + (dataCenterId));
        System.out.println("机房id左移位数:" + (DATA_CENTER_LEFT));
        System.out.println("机器id:" + (machineId));
        System.out.println("机器id左移位数:" + (MACHINE_LEFT));
        System.out.println("序列号:" + (sequence));

        long id = (currStamp - START_STAMP) << TIMESTAMP_LEFT // 时间戳部分
                | dataCenterId << DATA_CENTER_LEFT // 数据中心部分
                | machineId << MACHINE_LEFT // 机器标识部分
                | sequence; // 序列号部分

        System.out.println("id的二进制是:" + Long.toBinaryString(id));
        return id;
    }
    
    /**
     * 获得下一个毫秒数，比lastStamp大的下一个毫秒数
     *
     * @return
     */
    private long getNextMill() {
        long mill = getNewStamp();
        while (mill <= lastStamp) {
            mill = getNewStamp();
        }
        return mill;
    }
    
    /**
     * 获得当前毫秒数
     *
     * @return
     */
    private long getNewStamp() {
        return System.currentTimeMillis();
    }
    
    public static void main(String[] args) {
        SnowFlake snowFlake = new SnowFlake(3L, 10L);
        System.out.println("snowFlake.nextId() = " + snowFlake.nextId());
    }
}